'use strict';

var loopback = require('loopback');
var boot = require('loopback-boot');
var fs = require('fs');
var path = require('path');
var winston = require('winston');
var filename = path.join(__dirname, '..', 'socketStore.log');
winston.add(winston.transports.File, { filename: 'somefile.log' });


var app = module.exports = loopback();
app.set('view engine', 'pug');

app.start = function() {
  // start the web server
  return app.listen(function() {
    app.emit('started');
    var baseUrl = app.get('url').replace(/\/$/, '');
    console.log('Web server listening at: %s', baseUrl);
    if (app.get('loopback-component-explorer')) {
      var explorerPath = app.get('loopback-component-explorer').mountPath;
      console.log('Browse your REST API at %s%s', baseUrl, explorerPath);
    }
  });
};

var morgan = require('morgan');
app.use(morgan('tiny'))

app.socketStore = Object.assign(
  {
    tables: {},
    clients: {}
  },
  loadSocketStore(filename)
)

// Bootstrap the application, configure models, datasources and middleware.
// Sub-apps like REST API are mounted via boot scripts.
boot(app, __dirname, function(err) {
  if (err) throw err;

  // start the server if `$ node server.js`
  if (require.main === module) {
    app.io = require('socket.io')(app.start(), { 'pingInterval': 2000, 'pingTimeout': 5000 });
    app.sockets = [];
    app.mapSockets = {}
    const Match = app.models.Match

    // Should transfer {clientId, tableId}
    app.io.on('connection', function(socket) {
      console.log('A new user connected: ' + socket.id);
      // var matchId = socket.handshake.query["matchId"];
      var playerName = socket.handshake.query["playerName"];
      const clientId = socket.handshake.query["clientId"];
      const tableId = socket.handshake.query["tableId"];
      // console.log('matchId: ' + matchId + ', playerName: ' + playerName + ', clientId: ' + clientId);
      // socket.matchId = matchId;
      socket.playerName = playerName;

      socket.clientId = clientId;
      socket.tableId = tableId;

      console.log(`\n\nWelocome clientId: ${socket.clientId}, tableId: ${socket.tableId}\n\n`)

      app.sockets.push(socket);
      app.joinTable(tableId, clientId)

      // socket重连，推送Match
      const { user, matchId } = app.socketStore.clients[clientId] || {}
      if (user && matchId) {
        console.log(`===> 用户重连 userid=${user.id} ${user.position} matchId=${matchId} ====`)
        Match
          .findOne({
            where: { id: matchId },
            include: ['users'],
          })
          .then(match => {
            console.log('======== 成功 ========', !!match)

            const matchData = match.__data
            matchData.me = {
              userid: user.id,
              nickname: user.nickname,
              isHost: user.isHost,
              position: user.position
            }

            app.pushDataToClient('me', clientId, user)
            app.pushDataToClient('matchReconnect', clientId , matchData)
          })
          .catch(err => {
            console.log('======== 失败 ========')
            console.log(err)
          })
      }


      // socket.on('ping', function() {
      //   socket.emit('pong');
      // });

      socket.on('disconnect', function() {
        console.log('A user disconnected: ' + socket.id + ', ' + socket.playerName);
        var i = app.sockets.indexOf(socket);
        if (i != -1) {
          app.sockets.splice(i, 1);
        }
      });
    });

  }
});

app.pushData = function(matchId, channel, data) {
  if (app.sockets[matchId]) {
    console.log('===> ' + app.sockets[matchId].length + ' clients to notify');
    app.sockets[matchId] && app.sockets[matchId].forEach(function (socket) {
      console.log('socket emit ' + channel + ', ' + socket.id + ', ' + socket.playerName);
      socket.emit(channel, data);
    });
  }
}

app.broadcast = function(channel, data) {
  console.log('===> ' + app.sockets.length + ' clients to notify');
  app.sockets.forEach(function (socket) {
    console.log('socket emit ' + channel + ', ' + socket.id + ', ' + socket.playerName);
    socket.emit(channel, data);
  });
}

app.pushDataToClient = function(channel, clientId, data) {
  const socket = app.sockets.find( el => el.clientId == clientId)

  console.log(`====> ToClient ${channel} ${!!socket} ${!!data} ===========`)
  socket && socket.emit(channel, data)
}

app.broadcastToSome = function(channel, clientIds, data) {
  console.log(`====> broadcastToSome ${channel} ===========`)
  console.log('clientIds', clientIds)
  clientIds.forEach(clientId =>
    app.pushDataToClient(channel, clientId, data)
  )
}

/**
 * Binding user & matchId
 * @param  {string} clientId deviceId, unique
 * @param  {object} data     { user, matchId }
 */
app.bindSocket = function (clientId, user, matchId) {
  console.log('===== bindSocket 成功 =====')
  // console.log(Object.keys(user))
  console.log(user.id, user.nickname || user.name, matchId)
  app.socketStore.clients[clientId] = { user, matchId }

  saveSocketStore(app.socketStore, filename)
}

function loadSocketStore(filename) {
  const exists = fs.existsSync(filename)

  if (!exists) {
    return {}
  }

  const data = fs.readFileSync(filename, { encoding: 'utf8' })

  let jsonData = null

  try {
    jsonData = JSON.parse(data || '{}')
  } catch (error) {
    jsonData = {}
  }

  return jsonData
}

function saveSocketStore(data, filename) {
  if (typeof data !== 'string') {
    data = JSON.stringify(data)
  }

  fs.writeFile( filename, data, { encoding: 'utf8' }, (err) => {
    if (err) {
      console.log('The file has been saved failed!');
      console.log(err)
      throw err
    }

    console.log('The file has been saved!');
  });
}


// Consider, remove ClientId from table
app.unbindSocket = function (user) {
  console.log(`====> unbindSocket ===========`)
  Object.keys(app.socketStore.clients).forEach( clientId => {
    const { user: _user , matchId } = app.socketStore.clients[clientId]
    if (String(user.id) === String(_user.id)) {
      console.log(`====> unbindSocket 成功 ===========`)
      console.log(_user.id, _user.nickname || _user.name)
      delete app.socketStore.clients[clientId]
    }
  })

  saveSocketStore(app.socketStore, filename)
}

// Find clients on the same table & logined
app.getClientsFromTable = function (tableId) {
  const clientIds = []

  if (!Array.isArray(app.socketStore.tables[tableId])) {
    return []
  }

  app.socketStore.tables[tableId].forEach( clientId => {
    app.socketStore.clients[clientId] && clientIds.push(clientId)
  })

  return clientIds
}

// All clienta on the same user &table
app.getClientIdsFromUser = function (tableId, user) {
  const clientIds = []

  app.getClientsFromTable(tableId).forEach( clientId => {
    const { user: _user } = app.socketStore.clients[clientId] || {}
    String(_user.id) === String(user.id) && clientIds.push(clientId)
  })

  return clientIds
}


// Join Table, Maybe user logined.
app.joinTable = function (tableId, clientId) {
  if (!app.socketStore.tables[tableId]) {
    app.socketStore.tables[tableId] = [clientId]
  } else {
    if (!app.socketStore.tables[tableId].includes(clientId)) {
      app.socketStore.tables[tableId].push(clientId)
    }
  }
}

app.leaveTable = function (tableId, clientId) {
  if (app.socketStore.tables[tableId]) {
    app.socketStore.tables[tableId] = app.socketStore.tables[tableId].filter(_clientId => _clientId !== clientId)
  }
}
